Crate human_string_filler[][src]

Expand description

A tiny template language for human-friendly string substitutions.

This crate is intended for situations where you need the user to be able to write simple templated strings, and conveniently evaluate them. It’s deliberately simple so that there are no surprises in its performance or functionality, and so that it’s not accidentally tied to Rust (e.g. you can readily implement it in a JavaScript-powered web app), which would happen if things like number formatting specifiers were included out of the box—instead, if you want that sort of thing, you’ll have to implement it yourself (don’t worry, it won’t be hard).

No logic is provided in this template language, only simple string formatting: {…} template regions get replaced in whatever way you decide, curly braces get escaped by doubling them ({{ and }}), and that’s it.

Sample usage

The lowest-level handling looks like this:

use human_string_filler::{StrExt, SimpleFillerError};

let mut output = String::new();
"Hello, {name}!".fill_into(&mut output, |output: &mut String, key: &str| {
    match key {
        "name" => output.push_str("world"),
        _ => return Err(SimpleFillerError::NoSuchKey),
    }
    Ok(())
}).unwrap();

assert_eq!(output, "Hello, world!");

template.fill_into(output, filler) (provided by StrExt) can also be spelled fill(template, filler, output) if you prefer a function to a method (I reckon the method syntax is clearer, but opinions will differ so I provided both).

The filler function appends to the string directly for efficiency in case of computed values, and returns Result<(), E>; any error will become Err(Error::BadReplacement { error, .. }) on the fill call. (In this example I’ve used SimpleFillerError::NoSuchKey, but () would work almost as well, or you can write your own error type altogether.)

This example showed a closure that took &mut String and used .push_str(…), but this crate is not tied to String in any way: for greater generality you would use a function generic over a type that implements std::fmt::Write, and use .write_str(…)? inside (? works there because SimpleFillerError implements From<std::fmt::Error>).

At a higher level, you can use a string-string map as a filler, and you can also fill directly to a String with .fill_to_string() (also available as a standalone function fill_to_string):

use std::collections::HashMap;
use human_string_filler::StrExt;

let mut map = HashMap::new();
map.insert("name", "world");

let s = "Hello, {name}!".fill_to_string(&map);

assert_eq!(s.unwrap(), "Hello, world!");

Or you can implement the Filler trait for some other type of your own if you like.

Cargo features

  • std (enabled by default, enabled in this build): remove for #![no_std] operation. Implies alloc.

    • Implementation of std::error::Error for Error;
    • Implementation of Filler for &HashMap.
  • alloc (enabled by default via std, enabled in this build):

    • Implementation of Filler for &BTreeMap.
    • fill_to_string and StrExt::fill_to_string.

The template language

This is the grammar of the template language in ABNF:

unescaped-normal-char = %x00-7A / %x7C / %x7E-D7FF / %xE000-10FFFF
                      ; any Unicode scalar value except for "{" and "}"

normal-char           = unescaped-normal-char / "{{" / "}}"

template-region       = "{" *unescaped-normal-char "}"

template-string       = *( normal-char / template-region )

This regular expression will validate a template string:

^([^{}]|\{\{|\}\}|\{[^{}]*\})*$

Sample legal template strings:

  • The empty string
  • Hello, {name}!: one template region with key “name”.
  • Today is {date:short}: one template region with key “date:short”. (Although there’s no format specification like with the format!() macro, a colon convention is one reasonable option—see the next section.)
  • Hello, {}!: one template region with an empty key, not recommended but allowed.
  • Escaped {{ braces {and replacements} for {fun}!: string “Escaped { braces “, followed by a template region with key “and replacements”, followed by string “ for “, followed by a template region with key “fun”, followed by string “!”.

Sample illegal template strings:

  • hello, {world}foo}: opening and closing curlies must match; any others (specifically, the last character of the string) must be escaped by doubling.
  • {{thing}: the {{ is an escaped opening curly, so the } is unmatched.
  • {thi{{n}}g}: no curlies of any form inside template region keys. (It’s possible that a future version may make it possible to escape curlies inside template regions, if it proves to be useful in something like format specifiers; but not at this time.)

Conventions on key semantics

The key is an arbitrary string (except that it can’t contain { or }) with explicitly no defined semantics, but here are some suggestions, including helper functions:

  1. If it makes sense to have a format specifier (e.g. to specify a date format to use, or whether to pad numbers with leading zeroes, &c.), split once on a character like :. To do this most conveniently, a function split_on is provided.

  2. For more advanced formatting where you have multiple properties you could wish to set, split_propertied offers some sound and similarly simple semantics for such strings as {key prop1 prop2=val2} and {key:prop1,prop2=val2}.

  3. If it makes sense to have nested property access, split on . with the key.split('.') iterator. (If you’re using split_on or split_propertied as mentioned above, you probably want to apply them first to separate out the key part.)

  4. Only use UAX #31 identifiers for the key (or keys, if supporting nested property access). Most of the time, empty strings and numbers are probably not a good idea.

With these suggestions, you might end up with the key foo.bar:baz being interpreted as retrieving the “bar” property from the “foo” object, and formatting it according to “baz”; or aleph.beth.gimmel|alpha beta=5 as retrieving “gimmel” from “beth” of “aleph”, and formatting it with properties “alpha” set to true and “beta” set to 5. What those things actually mean is up to you to decide. I certainly haven’t a clue.

Structs

The separators to use in split_propertied.

Enums

Any error that occurs when filling a template string.

A convenient error type for fillers; you might even like to use it yourself.

Traits

Implementers of this trait have the ability to fill template strings.

String extension methods for the template string.

Functions

The lowest-level form, as a function: fill the template string, into a provided writer.

Fill a template, producing a new string.

A convenience function to split a string on a character.

A convenience function to split a key that is followed by properties.